/* NOTICE
 
    Jim - the Java Image Manager, copyright (c) 2005 - 2007 Grant Gardner 
 
    Jim is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Jim is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  
    In addition, as a special exception, you are permitted to link this code
    with the 3rd party libraries required to compile this file and to
    distribute a linked combination including them. You must obey the
    GNU General Public License in all respects for all of the code except for
    those 3rd party libraries that are licenced under terms incompatible with the
    GNU General Public License.  
 
 
 NOTICE */

/*
 * Created on 12/07/2004
 *
 */

package au.com.lastweekend.jim.io.jpeg;

import org.apache.log4j.Logger;

/**
 * The spec gives procedural examples for creating one-dimensional arrays (tables) for the Huffman decoding process. In Java we
 * have two dimensional arrays available to us, such that some of the intermediate tables are not required. eg, BITS[i] =
 * HUFFVAL[i].length, so HUFFVAL is all we need to carry around.
 * 
 * The mapping of code to values is either something that is provided by the spec or is something that is specified by the Huffman
 * algorithm itself. I don't know which. But ends up as...
 * 
 * value(code) = huffvalues[code.numberOfBits][code - minimumCode[code.numberOfBits]
 * 
 * Encoding is simply the reverse of the above. As we are only encoding bytes, I simply maintain 2 255 element arrays, being the
 * equivalents of EHUFSI and EHUFCO.
 * 
 * Both the encoder and decoder are iniitialised at construction.
 * 
 * @version $Id: HuffmanTable.java,v 1.3 2006/01/28 05:05:40 ggardner Exp $
 * @author grant@lastweekend.com.au
 * 
 */
public class HuffmanTable {

    private static final Logger LOG = Logger.getLogger(HuffmanTable.class);
    private byte[][] _huffmanValues;
    private int[] _minCode;
    private int[] _maxCode;

    private byte[] _sizes;
    private int[] _codes;

    /**
     * @param huffmanValues
     */
    public HuffmanTable(byte[][] huffmanValues) {

        _huffmanValues = huffmanValues;
        initialiseDecoder();
        initialiseEncoder();
    }

    private void initialiseDecoder() {

        if (_minCode != null) {
            return;
        }

        _minCode = new int[16];
        _maxCode = new int[16];

        int code = 0;
        for (int i = 0; i < 16; i++) {
            int numberOfCodes = _huffmanValues[i].length;
            _maxCode[i] = -1;
            _minCode[i] = code;
            for (int j = 0; j < numberOfCodes; j++) {
                _maxCode[i] = code;
                code++;
            }
            code = code << 1;
        }
    }

    /**
     * @param bitBuffer
     * @return unsigned byte being the decoded value
     * @throws JpegProcessingException
     */
    public byte decode(JpegBitBuffer bitBuffer) throws JpegProcessingException {

        int code = bitBuffer.getBits(1);
        int codeLength = 0;
        while (code > _maxCode[codeLength] && codeLength < 16) {
            code = code << 1;
            code = code + bitBuffer.getBits(1);
            codeLength++;
            if (codeLength > 15) {
                throw new JpegProcessingException("Unknown huffman code");
            }
        }

        int valueIndex = code - _minCode[codeLength];

        return _huffmanValues[codeLength][valueIndex];

    }

    /**
     * @param value
     * @param bitOutput
     * @throws JpegProcessingException
     */
    public void encode(int value, JpegBitOutput bitOutput) throws JpegProcessingException {

        int indexValue = value & 0xFF;
        bitOutput.writeBits(_sizes[indexValue], _codes[indexValue]);
    }

    /**
     * 
     */
    private void initialiseEncoder() {

        if (_codes != null) {
            return;
        }
        _codes = new int[255];
        _sizes = new byte[255];
        int code = 0;
        for (byte i = 0; i < 16; i++) {
            for (int j = 0; j < _huffmanValues[i].length; j++) {
                int value = (_huffmanValues[i][j] & 0xFF);
                _sizes[value] = (byte) (i + 1);
                _codes[value] = code;
                code++;
            }
            code = code << 1;
        }
        if (LOG.isDebugEnabled()) {
            for (int i = 0; i < 255; i++) {
                LOG.debug(i + "\t" + Integer.toString(_sizes[i] + 1) + "\t" + Integer.toBinaryString(_codes[i]));
            }
        }
    }
}